/**
 *
 */
package vn.smartware.co.java.net.commons;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * BytesOutputStream
 * @author thuctt
 */
public class BytesOutputStream extends OutputStream {

    private static final int DEFAULT_INITIAL_BUFFER_SIZE = 8192;
    private static Log log = LogFactory.getLog(BytesOutputStream.class);
    
    // internal buffer
    private byte[] buffer;
    private int index;
    private int capacity;
    
    // is the stream closed ?
    private boolean closed;
    
    // is the buffer shared ?
    private boolean shared;
    
    
    /**
     * Constructor
     */
    public BytesOutputStream() {
        this(DEFAULT_INITIAL_BUFFER_SIZE);
    }
    
    
    /**
     * @param initialBufferSize
     */
    public BytesOutputStream(int initialBufferSize) {
        capacity = initialBufferSize;
        buffer = new byte[capacity];
    }
    
   /**
	* @param datum 
	* @throws IOException 
    */
    public void write(int datum) throws IOException {
        if (closed) {
            throw new IOException("Stream closed.");
        } else {
            if (index >= capacity) {
                
                // extend the internal buffer
                capacity = capacity * 2 + 1;
                byte[] tmp = new byte[capacity];
                System.arraycopy(buffer, 0, tmp, 0, index);
                buffer = tmp;
                
                // the new buffer is not shared
                shared = false;
            }
            
            // store the byte
            buffer[index++] = (byte)datum;
        }
    }
    
    /**
     * @param data 
     * @param offset 
     * @param length 
     * @throws IOException 
     */
    public void write(byte[] data, int offset, int length) throws IOException {
        
        if (data == null) {
            throw new NullPointerException();
        } else if ((offset < 0) || (offset + length > data.length) 
                || (length < 0)) {
            throw new IndexOutOfBoundsException();
        } else if (closed) {
            throw new IOException("Stream closed.");
        } else {
            if (index + length > capacity) {
                
                log.debug("extends the internal buffer.");
                
                capacity = capacity * 2 + length;
                byte[] tmp = new byte[capacity];
                System.arraycopy(buffer, 0, tmp, 0, index);
                buffer = tmp;
                
                log.debug("capacity = " + capacity);
                
                // the new buffer is not shared
                shared = false;
            }
            
            System.arraycopy(data, offset, buffer, index, length);
            index += length;
        }
    }

    /**
     * toByteArray
     * @return byte[]
     */
    public byte[] toByteArray() {
        // return a copy of the internal buffer
        byte[] result = new byte[index];
        System.arraycopy(buffer, 0, result, 0, index);
        return result;
    }
    
    /**
     * writeTo
     * @param out
     * @throws IOException
     */
    public void writeTo(OutputStream out) throws IOException {
        // write the internal buffer directly
        out.write(buffer, 0, index);
    }

    /**
     * toInputStream
     * @return InputStream
     */
    public InputStream toInputStream() {
        // return a stream reading from the shared internal buffer
        shared = true;
        return (InputStream)new BytesInputStream(buffer, 0, index);
    }
    
    /**
     * reset
     * @throws IOException
     */
    public void reset() throws IOException {
        if (closed) {
            throw new IOException("Stream closed.");
        } else {
            if (shared) {
                // create a new buffer it is shared
                buffer = new byte[capacity];
                shared = false;
            }
            
            // reset index
            index = 0;
        }
    }
}
